package com.richard.room.operator.api;

import android.text.TextUtils;

import androidx.annotation.NonNull;

import com.richard.room.operator.api.builder.ChildQuery;
import com.richard.room.operator.api.condition.PropertyCondition;
import com.richard.room.operator.api.statement.element.Expression;
import com.richard.room.operator.api.statement.element.Function;
import com.richard.room.operator.api.type.ExpressionType;
import com.richard.room.operator.api.type.FuncType;

import java.io.Serializable;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;

/**
 * @author: Richard
 * @createDate: 2023/8/7 14:17
 * @version: 1.0
 * @description: Entity 属性信息
 * timeFormat:
 * %d	一月中的第几天，01-31
 * %f	带小数部分的秒，SS.SSS
 * %H	小时，00-23
 * %j	一年中的第几天，001-366
 * %J	儒略日数，DDDD.DDDD
 * %m	月，00-12
 * %M	分，00-59
 * %s	从 1970-01-01 算起的秒数
 * %S	秒，00-59
 * %w	一周中的第几天，0-6 (0 is Sunday)
 * %W	一年中的第几周，01-53
 * %Y	年，YYYY
 * %%	% symbol
 */
public class Property implements Serializable {

    /**
     * 该属性所属表名
     */
    public final String tableName;

    /**
     * 在java中的列名
     */
    public final String javaName;

    /**
     * 数据表字段属性名称
     */
    public final String name;

    /**
     * 属性类型
     */
    public final Class<?> type;

    /**
     * 是否属于主键Id
     */
    public final boolean isId;

    /**
     * 该属性设置的默认值
     */
    public final String defaultValue;

    /**
     * 该Property对象是否属于复制对象，为了解决多次调用例如alias、distinct的方法时所重复产生的新对象的问题
     */
    private final boolean isClone;

    /**
     * 数据表字段别名
     */
    private String alias;

    /**
     * 该字段属性选择使用的sqlite函数
     */
    private Function function;

    public Property(String tableName, String javaName, String name, Class<?> type,
                    boolean isId, String defaultValue) {
        this(tableName, javaName, name, type, isId, defaultValue, false);
    }

    private Property(String tableName, String javaName, String name, Class<?> type,
                     boolean isId, String defaultValue, boolean isClone) {
        this.tableName = tableName;
        this.javaName = javaName;
        this.name = name;
        this.type = type;
        this.isId = isId;
        this.defaultValue = defaultValue;
        this.isClone = isClone;
    }

    /**
     * 获取或新建新的Property
     */
    private Property getOrClone() {
        if (isClone) {
            return this;
        }

        return new Property(
                tableName
                , javaName
                , name
                , type
                , isId
                , defaultValue
                , true
        );
    }

    /**
     * 更新并获取Property对象
     *
     * @param property  属性
     * @param alias     字段别名
     * @param funcType  指定使用的函数类型
     * @param funcValue 指定使用的函数参数
     */
    private Property update(Property property, String alias, FuncType funcType, Object[] funcValue) {
        if (!TextUtils.isEmpty(alias)) {
            if (!TextUtils.isEmpty(property.alias)) {
                throw new IllegalArgumentException(String.format("%s字段属性已存在别名%s", property.name, property.alias));
            }
            property.alias = alias;
        }

        if (funcType != null) {
            if (property.function != null) {
                throw new IllegalArgumentException(String.format("%s字段属性已存在指定函数%s", property.name, property.function.type.name()));
            }
            property.function = new Function(funcType, property, funcValue);
        }

        return property;
    }

    public String getAlias() {
        return alias;
    }

    public Function getFunction() {
        return function;
    }

    public Property alias(String alias) {
        return this.update(this.getOrClone(), alias, null, null);
    }

    public Property count() {
        return this.update(this.getOrClone(), null, FuncType.count, null);
    }

    public Property max() {
        return this.update(this.getOrClone(), null, FuncType.max, null);
    }

    public Property min() {
        return this.update(this.getOrClone(), null, FuncType.min, null);
    }

    public Property avg() {
        return this.update(this.getOrClone(), null, FuncType.avg, null);
    }

    public Property avg(int decimalPlaces) {
        return this.update(this.getOrClone(), null, FuncType.avg, new Object[]{decimalPlaces});
    }

    public Property sum() {
        return this.update(this.getOrClone(), null, FuncType.sum, null);
    }

    public Property sum(int decimalPlaces) {
        return this.update(this.getOrClone(), null, FuncType.sum, new Object[]{decimalPlaces});
    }

    public Property abs() {
        return this.update(this.getOrClone(), null, FuncType.abs, null);
    }

    public Property upper() {
        return this.update(this.getOrClone(), null, FuncType.upper, null);
    }

    public Property lower() {
        return this.update(this.getOrClone(), null, FuncType.lower, null);
    }

    public Property length() {
        return this.update(this.getOrClone(), null, FuncType.length, null);
    }

    public Property formatTime(String format) {
        return this.update(this.getOrClone(), null, FuncType.strFTime, new Object[]{format});
    }

    public Property dateTime() {
        return this.update(this.getOrClone(), null, FuncType.dateTime, null);
    }

    public Property date() {
        return this.update(this.getOrClone(), null, FuncType.date, null);
    }

    public Property time() {
        return this.update(this.getOrClone(), null, FuncType.time, null);
    }

    public Property trim() {
        return this.update(this.getOrClone(), null, FuncType.trim, null);
    }

    public Property ifNull(Object defaultValue) {
        Property p = this.getOrClone();
        return this.update(p, null, FuncType.ifNull, new Object[]{p, defaultValue});
    }

    public Property round(int decimalPlaces) {
        Property p = this.getOrClone();
        return this.update(p, null, FuncType.round, new Object[]{p, decimalPlaces});
    }

    public Property subStr(int start, int len) {
        Property p = this.getOrClone();
        return this.update(p, null, FuncType.subStr, new Object[]{p, start, len});
    }

    public Property lTrim(Object value) {
        Property p = this.getOrClone();
        return this.update(p, null, FuncType.lTrim, new Object[]{p, value});
    }

    public Property rTrim(Object value) {
        Property p = this.getOrClone();
        return this.update(p, null, FuncType.rTrim, new Object[]{p, value});
    }

    public Property replace(Object value1, Object value2) {
        Property p = this.getOrClone();
        return this.update(p, null, FuncType.replace, new Object[]{p, value1, value2});
    }

    @NonNull
    @Override
    public String toString() {
        return this.name;
    }

    //--------------------------------------isNull and notNull--------------------------------------

    public PropertyCondition isNull() {
        return new PropertyCondition(new Expression(this, ExpressionType.isNull));
    }

    public PropertyCondition notNull() {
        return new PropertyCondition(new Expression(this, ExpressionType.notNull));
    }

    //----------------------------------------equal(=)-------------------------------------------------

    public PropertyCondition equal(Property property) {
        return new PropertyCondition(new Expression(this, ExpressionType.equal, new Object[]{property}, true));
    }

    public PropertyCondition equal(long value) {
        return new PropertyCondition(new Expression(this, ExpressionType.equal, new Object[]{value}));
    }

    public PropertyCondition equal(double value) {
        return new PropertyCondition(new Expression(this, ExpressionType.equal, new Object[]{value}));
    }

    public PropertyCondition equal(byte[] value) {
        return new PropertyCondition(new Expression(this, ExpressionType.equal, new Object[]{value}));
    }

    public PropertyCondition equal(String value) {
        return new PropertyCondition(new Expression(this, ExpressionType.equal, new Object[]{value}));
    }

    public PropertyCondition equal(boolean value) {
        return new PropertyCondition(new Expression(this, ExpressionType.equal, new Object[]{value}));
    }

    public PropertyCondition equal(Date value) {
        return new PropertyCondition(new Expression(this, ExpressionType.equal, new Object[]{value.getTime()}));
    }

    public PropertyCondition equal(ChildQuery childQuery) {
        return new PropertyCondition(new Expression(this, ExpressionType.equal, childQuery.getSql()));
    }

    //---------------------------------------notEqual(!=)-----------------------------------------------

    public PropertyCondition notEqual(Property property) {
        return new PropertyCondition(new Expression(this, ExpressionType.notEqual, new Object[]{property}, true));
    }

    public PropertyCondition notEqual(long value) {
        return new PropertyCondition(new Expression(this, ExpressionType.notEqual, new Object[]{value}));
    }

    public PropertyCondition notEqual(double value) {
        return new PropertyCondition(new Expression(this, ExpressionType.notEqual, new Object[]{value}));
    }

    public PropertyCondition notEqual(byte[] value) {
        return new PropertyCondition(new Expression(this, ExpressionType.notEqual, new Object[]{value}));
    }

    public PropertyCondition notEqual(String value) {
        return new PropertyCondition(new Expression(this, ExpressionType.notEqual, new Object[]{value}));
    }

    public PropertyCondition notEqual(boolean value) {
        return new PropertyCondition(new Expression(this, ExpressionType.notEqual, new Object[]{value}));
    }

    public PropertyCondition notEqual(Date value) {
        return new PropertyCondition(new Expression(this, ExpressionType.notEqual, new Object[]{value.getTime()}));
    }

    public PropertyCondition notEqual(ChildQuery childQuery) {
        return new PropertyCondition(new Expression(this, ExpressionType.notEqual, childQuery.getSql()));
    }

    //-----------------------------------------less(<)----------------------------------------------
    public PropertyCondition less(Property property) {
        return new PropertyCondition(new Expression(this, ExpressionType.less, new Object[]{property}, true));
    }

    public PropertyCondition less(long value) {
        return new PropertyCondition(new Expression(this, ExpressionType.less, new Object[]{value}));
    }

    public PropertyCondition less(double value) {
        return new PropertyCondition(new Expression(this, ExpressionType.less, new Object[]{value}));
    }

    public PropertyCondition less(byte[] value) {
        return new PropertyCondition(new Expression(this, ExpressionType.less, new Object[]{value}));
    }

    public PropertyCondition less(String value) {
        return new PropertyCondition(new Expression(this, ExpressionType.less, new Object[]{value}));
    }

    public PropertyCondition less(Date value) {
        return new PropertyCondition(new Expression(this, ExpressionType.less, new Object[]{value.getTime()}));
    }

    public PropertyCondition less(ChildQuery childQuery) {
        return new PropertyCondition(new Expression(this, ExpressionType.less, childQuery.getSql()));
    }

    //----------------------------------------lessOrEqual(<=)-------------------------------------------------

    public PropertyCondition lessOrEqual(Property property) {
        return new PropertyCondition(new Expression(this, ExpressionType.lessOrEqual, new Object[]{property}, true));
    }

    public PropertyCondition lessOrEqual(long value) {
        return new PropertyCondition(new Expression(this, ExpressionType.lessOrEqual, new Object[]{value}));
    }

    public PropertyCondition lessOrEqual(double value) {
        return new PropertyCondition(new Expression(this, ExpressionType.lessOrEqual, new Object[]{value}));
    }

    public PropertyCondition lessOrEqual(byte[] value) {
        return new PropertyCondition(new Expression(this, ExpressionType.lessOrEqual, new Object[]{value}));
    }

    public PropertyCondition lessOrEqual(String value) {
        return new PropertyCondition(new Expression(this, ExpressionType.lessOrEqual, new Object[]{value}));
    }

    public PropertyCondition lessOrEqual(Date value) {
        return new PropertyCondition(new Expression(this, ExpressionType.lessOrEqual, new Object[]{value.getTime()}));
    }

    public PropertyCondition lessOrEqual(ChildQuery childQuery) {
        return new PropertyCondition(new Expression(this, ExpressionType.lessOrEqual, childQuery.getSql()));
    }

    //----------------------------------------greater(>)-------------------------------------------------

    public PropertyCondition greater(Property property) {
        return new PropertyCondition(new Expression(this, ExpressionType.greater, new Object[]{property}, true));
    }

    public PropertyCondition greater(long value) {
        return new PropertyCondition(new Expression(this, ExpressionType.greater, new Object[]{value}));
    }

    public PropertyCondition greater(double value) {
        return new PropertyCondition(new Expression(this, ExpressionType.greater, new Object[]{value}));
    }

    public PropertyCondition greater(byte[] value) {
        return new PropertyCondition(new Expression(this, ExpressionType.greater, new Object[]{value}));
    }

    public PropertyCondition greater(String value) {
        return new PropertyCondition(new Expression(this, ExpressionType.greater, new Object[]{value}));
    }

    public PropertyCondition greater(Date value) {
        return new PropertyCondition(new Expression(this, ExpressionType.greater, new Object[]{value.getTime()}));
    }

    public PropertyCondition greater(ChildQuery childQuery) {
        return new PropertyCondition(new Expression(this, ExpressionType.greater, childQuery.getSql()));
    }

    //-------------------------------greaterOrEqual(>=)---------------------------------------------

    public PropertyCondition greaterOrEqual(Property property) {
        return new PropertyCondition(new Expression(this, ExpressionType.greaterOrEqual, new Object[]{property}, true));
    }

    public PropertyCondition greaterOrEqual(long value) {
        return new PropertyCondition(new Expression(this, ExpressionType.greaterOrEqual, new Object[]{value}));
    }

    public PropertyCondition greaterOrEqual(double value) {
        return new PropertyCondition(new Expression(this, ExpressionType.greaterOrEqual, new Object[]{value}));
    }

    public PropertyCondition greaterOrEqual(byte[] value) {
        return new PropertyCondition(new Expression(this, ExpressionType.greaterOrEqual, new Object[]{value}));
    }

    public PropertyCondition greaterOrEqual(String value) {
        return new PropertyCondition(new Expression(this, ExpressionType.greaterOrEqual, new Object[]{value}));
    }

    public PropertyCondition greaterOrEqual(Date value) {
        return new PropertyCondition(new Expression(this, ExpressionType.greaterOrEqual, new Object[]{value.getTime()}));
    }

    public PropertyCondition greaterOrEqual(ChildQuery childQuery) {
        return new PropertyCondition(new Expression(this, ExpressionType.greaterOrEqual, childQuery.getSql()));
    }

    //-----------------------------------between(between...and...)----------------------------------

    public PropertyCondition between(long value1, long value2) {
        return new PropertyCondition(new Expression(this, ExpressionType.between, new Object[]{value1, value2}));
    }

    public PropertyCondition between(double value1, double value2) {
        return new PropertyCondition(new Expression(this, ExpressionType.between, new Object[]{value1, value2}));
    }

    public PropertyCondition between(Date value1, Date value2) {
        return new PropertyCondition(new Expression(this, ExpressionType.between, new Object[]{value1.getTime(), value2.getTime()}));
    }

    //------------------------------------in(in(?,?,?))---------------------------------------------
    public PropertyCondition in(Integer... value) {
        return new PropertyCondition(new Expression(this, ExpressionType.in, Arrays.asList(value).toArray()));
    }

    public PropertyCondition in(Long... value) {
        return new PropertyCondition(new Expression(this, ExpressionType.in, Arrays.asList(value).toArray()));
    }

    public PropertyCondition in(String... value) {
        return new PropertyCondition(new Expression(this, ExpressionType.in, Arrays.asList(value).toArray()));
    }

    public PropertyCondition in(Collection<?> value) {
        return new PropertyCondition(new Expression(this, ExpressionType.in, value.toArray()));
    }

    public PropertyCondition in(ChildQuery childQuery) {
        return new PropertyCondition(new Expression(this, ExpressionType.in, childQuery.getSql()));
    }

    //------------------------------------like(like 自定义表达式规则匹配)-----------------------------------------

    public PropertyCondition like(String exp) {
        return new PropertyCondition(new Expression(this, ExpressionType.like, new Object[]{exp}));
    }

    public PropertyCondition like(ChildQuery childQuery) {
        return new PropertyCondition(new Expression(this, ExpressionType.like, childQuery.getSql()));
    }

    public PropertyCondition like(Property property) {
        return new PropertyCondition(new Expression(this, ExpressionType.like, new Object[]{property}, true));
    }

    //------------------------------------contains(contains '%?%')-----------------------------------------

    public PropertyCondition contains(String value) {
        return new PropertyCondition(new Expression(this, ExpressionType.contains, new Object[]{value}));
    }

    public PropertyCondition contains(ChildQuery childQuery) {
        return new PropertyCondition(new Expression(this, ExpressionType.contains, childQuery.getSql()));
    }

    public PropertyCondition contains(Property value) {
        return new PropertyCondition(new Expression(this, ExpressionType.contains, new Object[]{value}, true));
    }

    //------------------------------------startWith(like '?%')-----------------------------------------

    public PropertyCondition startWith(String value) {
        return new PropertyCondition(new Expression(this, ExpressionType.startWith, new Object[]{value}));
    }

    public PropertyCondition startWith(ChildQuery childQuery) {
        return new PropertyCondition(new Expression(this, ExpressionType.startWith, childQuery.getSql()));
    }

    public PropertyCondition startWith(Property value) {
        return new PropertyCondition(new Expression(this, ExpressionType.startWith, new Object[]{value}, true));
    }

    //------------------------------------endWith(like '%?')-----------------------------------------

    public PropertyCondition endWith(String value) {
        return new PropertyCondition(new Expression(this, ExpressionType.endWith, new Object[]{value}));
    }

    public PropertyCondition endWith(ChildQuery childQuery) {
        return new PropertyCondition(new Expression(this, ExpressionType.endWith, childQuery.getSql()));
    }

    public PropertyCondition endWith(Property value) {
        return new PropertyCondition(new Expression(this, ExpressionType.endWith, new Object[]{value}, true));
    }

    //------------------------------------regExp(regExp '^a.*')-----------------------------------------

    public PropertyCondition regExp(String regExp) {
        return new PropertyCondition(new Expression(this, ExpressionType.regExp, new Object[]{regExp}));
    }

    public PropertyCondition regExp(ChildQuery childQuery) {
        return new PropertyCondition(new Expression(this, ExpressionType.regExp, childQuery.getSql()));
    }

    public PropertyCondition regExp(Property regExp) {
        return new PropertyCondition(new Expression(this, ExpressionType.regExp, new Object[]{regExp}, true));
    }
}
